

#include "CColladaFileLoader.h"
#include "os.h"
#include <string.h>
#include "IXMLReader.h"
#include "IDummyTransformationSceneNode.h"
#include "SAnimatedMesh.h"
#include "fast_atof.h"
#include "quaternion.h"
#include "ILightSceneNode.h"
#include "ICameraSceneNode.h"
#include "IMeshManipulator.h"
#include "IStringParameters.h"
#include "IMeshCache.h"

namespace irr
{
    namespace scene
    {
        // currently supported COLLADA tag names

        const core::stringc colladaSectionName =   "COLLADA";
        const core::stringc librarySectionName =   "library";
        const core::stringc assetSectionName =     "asset";
        const core::stringc sceneSectionName =     "scene";

        const core::stringc lightPrefabName =      "light";
        const core::stringc cameraPrefabName =     "camera";
        const core::stringc materialSectionName =  "material";
        const core::stringc geometrySectionName =  "geometry";

        const core::stringc meshSectionName =      "mesh";
        const core::stringc sourceSectionName =	   "source";
        const core::stringc arraySectionName =     "array";
        const core::stringc accessorSectionName =  "accessor";
        const core::stringc verticesSectionName =  "vertices";
        const core::stringc inputTagName =         "input";
        const core::stringc polygonsSectionName =  "polygons";
        const core::stringc polygonName =          "p";
        const core::stringc nodeSectionName =      "node";
        const core::stringc lookatNodeName =       "lookat";
        const core::stringc matrixNodeName =       "matrix";
        const core::stringc perspectiveNodeName =  "perspective";
        const core::stringc rotateNodeName =       "rotate";
        const core::stringc scaleNodeName =        "scale";
        const core::stringc translateNodeName =    "translate";
        const core::stringc skewNodeName =         "skew";
        const core::stringc instanceNodeName =     "instance";

        const core::stringc paramTagName =         "param";

        const char* const inputSemanticNames[] = {"POSITION", "VERTEX", "NORMAL", "TEXCOORD",
            "UV", "TANGENT", 0};

        //! following class is for holding and createing instances of library objects,
        //! named prefabs in this loader.
        class CPrefab : public IColladaPrefab
        {
        public:

            CPrefab(const char* id)
            {
                Id = id;
            }

            //! creates an instance of this prefab
            virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent,
                scene::ISceneManager* mgr)
            {
                // empty implementation
                return 0;
            }

            //! returns id of this prefab
            virtual const c8* getId()
            {
                return Id.c_str();
            }

        protected:

            core::stringc Id;
        };


        //! prefab for a light scene node
        class CLightPrefab : public CPrefab
        {
        public:

            CLightPrefab(const char* id) : CPrefab(id) 
            {
#ifdef COLLADA_READER_DEBUG
                os::Printer::log("COLLADA: loaded light prefab:", Id.c_str());
#endif
            }

            video::SLight LightData; // public accessible

            //! creates an instance of this prefab
            virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent,
                scene::ISceneManager* mgr)
            {
#ifdef COLLADA_READER_DEBUG
                os::Printer::log("COLLADA: Constructing light instance:", Id.c_str());
#endif

                scene::ILightSceneNode* l = mgr->addLightSceneNode(parent);
                l->getLightData() = LightData;
                return l;
            }
        };


        //! prefab for a mesh scene node
        class CGeometryPrefab : public CPrefab
        {
        public:

            CGeometryPrefab(const char* id) : CPrefab(id) 
            {
            }

            scene::IMesh* Mesh; // public accessible

            //! creates an instance of this prefab
            virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent,
                scene::ISceneManager* mgr)
            {
#ifdef COLLADA_READER_DEBUG
                os::Printer::log("COLLADA: Constructing mesh instance:", Id.c_str());
#endif

                scene::ISceneNode* m = mgr->addMeshSceneNode(Mesh, parent);
                return m;
            }
        };


        //! prefab for a camera scene node
        class CCameraPrefab : public CPrefab
        {
        public:

            CCameraPrefab(const char* id) 
                : CPrefab(id), YFov(core::PI / 2.5f), ZNear(1.0f), ZFar(3000.0f)
            {
#ifdef COLLADA_READER_DEBUG
                os::Printer::log("COLLADA: loaded camera prefab:", Id.c_str());
#endif
            }

            // public accessible data
            f32 YFov;
            f32 ZNear;
            f32 ZFar;

            //! creates an instance of this prefab
            virtual scene::ISceneNode* addInstance(scene::ISceneNode* parent,
                scene::ISceneManager* mgr)
            {
#ifdef COLLADA_READER_DEBUG
                os::Printer::log("COLLADA: Constructing camera instance:", Id.c_str());
#endif

                scene::ICameraSceneNode* c = mgr->addCameraSceneNode(parent);
                c->setFOV(YFov);
                c->setNearValue(ZNear);
                c->setFarValue(ZFar);
                return c;
            }
        };

        //! Constructor
        CColladaFileLoader::CColladaFileLoader(video::IVideoDriver* driver, 
            scene::ISceneManager* smgr, io::IFileSystem* fs)
            : Driver(driver), SceneManager(smgr), FileSystem(fs), DummyMesh(0),
            CreateInstances(false)
        {

        }


        //! destructor
        CColladaFileLoader::~CColladaFileLoader()
        {
            if (DummyMesh)
                DummyMesh->drop();
        }


        //! Returns true if the file maybe is able to be loaded by this class.
        /** This decision should be based only on the file extension (e.g. ".cob") */
        bool CColladaFileLoader::isALoadableFileExtension(const c8* fileName)
        {
            return strstr(fileName, ".xml") ||
                strstr(fileName, ".dae");
        }


        //! creates/loads an animated mesh from the file.
        //! \return Pointer to the created mesh. Returns 0 if loading failed.
        //! If you no longer need the mesh, you should call IAnimatedMesh::drop().
        //! See IUnknown::drop() for more information.
        IAnimatedMesh* CColladaFileLoader::createMesh(irr::io::IReadFile* file)
        {
            io::IXMLReaderUTF8* reader = FileSystem->createXMLReaderUTF8(file);
            if (!reader)
                return 0;

            CurrentlyLoadingMesh = file->getFileName();
            CreateInstances = SceneManager->getParameters()->getParameterAsBool(
                scene::COLLADA_CREATE_SCENE_INSTANCES);

            // read until COLLADA section, skip other parts

            while(reader->read())
            {
                if (reader->getNodeType() == io::EXN_ELEMENT)
                {
                    if (colladaSectionName == reader->getNodeName())
                        readColladaSection(reader);
                    else
                        skipSection(reader, true); // unknown section
                }
            }

            reader->drop();

            scene::IAnimatedMesh* returnMesh = DummyMesh;

            // because this loader loads and creates a complete scene instead of 
            // a single mesh, return an empty dummy mesh to make the scene manager
            // know that everything went well.
            if (!DummyMesh)
            {
                DummyMesh = new SAnimatedMesh();
                returnMesh = DummyMesh;
            }

            // clean up temporary loaded data
            clearData();

            returnMesh->grab(); // store until this loader is destroyed

            DummyMesh->drop();
            DummyMesh = 0;

            return returnMesh;
        }



        //! skips an (unknown) section in the collada document
        void CColladaFileLoader::skipSection(io::IXMLReaderUTF8* reader, bool reportSkipping)
        {
#ifndef COLLADA_READER_DEBUG
            if (reportSkipping) // always report in COLLADA_READER_DEBUG mode
#endif
                os::Printer::log("COLLADA skipping section", core::stringc(reader->getNodeName()).c_str());

            // skip if this element is empty anyway.
            if (reader->isEmptyElement())
                return;

            // read until we've reached the last element in this section
            int tagCounter = 1;

            while(tagCounter && reader->read())
            {
                if (reader->getNodeType() == io::EXN_ELEMENT &&
                    !reader->isEmptyElement())
                {
#ifdef COLLADA_READER_DEBUG
                    if (reportSkipping)
                        os::Printer::log("COLLADA unknown element:", core::stringc(reader->getNodeName()).c_str());
#endif // COLLADA_READER_DEBUG

                    ++tagCounter;
                }
                else
                    if (reader->getNodeType() == io::EXN_ELEMENT_END)
                        --tagCounter;
            }
        }


        //! reads the <COLLADA> section and its content
        void CColladaFileLoader::readColladaSection(io::IXMLReaderUTF8* reader)
        {
            if (reader->isEmptyElement())
                return;

            // I ignore version information here. Keep on reading content:

            while(reader->read())
                if (reader->getNodeType() == io::EXN_ELEMENT)
                {
                    if (librarySectionName == reader->getNodeName())
                        readLibrarySection(reader);
                    else
                        if (assetSectionName == reader->getNodeName())
                            readAssetSection(reader);
                        else
                            if (sceneSectionName == reader->getNodeName())
                                readSceneSection(reader);
                            else
                                skipSection(reader, true); // unknown section
                }
        }



        //! reads a <library> section and its content
        void CColladaFileLoader::readLibrarySection(io::IXMLReaderUTF8* reader)
        {
            while(reader->read())
                if (reader->getNodeType() == io::EXN_ELEMENT)
                {
                    if (lightPrefabName == reader->getNodeName())
                        readLightPrefab(reader);
                    else
                        if (materialSectionName == reader->getNodeName())
                            readMaterial(reader);
                        else
                            if (cameraPrefabName == reader->getNodeName())
                                readCameraPrefab(reader);
                            else
                                if (geometrySectionName == reader->getNodeName())
                                    readGeometry(reader);	
                                else
                                    skipSection(reader, true); // unknown section
                }
                else
                    if (reader->getNodeType() == io::EXN_ELEMENT_END)
                    {
                        if (librarySectionName == reader->getNodeName())
                            break; // end reading.
                    }
        }


        //! reads a <scene> section and its content
        void CColladaFileLoader::readSceneSection(io::IXMLReaderUTF8* reader)
        {
#ifdef COLLADA_READER_DEBUG
            os::Printer::log("COLLADA reading scene");
#endif

            if (reader->isEmptyElement())
                return;

            // read the scene

            while(reader->read())
                if (reader->getNodeType() == io::EXN_ELEMENT)
                {
                    if (nodeSectionName == reader->getNodeName())
                        readNodeSection(reader, SceneManager->getRootSceneNode());
                    else
                        skipSection(reader, true); // ignore all other sections
                }
        }



        //! reads a <asset> section and its content
        void CColladaFileLoader::readAssetSection(io::IXMLReaderUTF8* reader)
        {
#ifdef COLLADA_READER_DEBUG
            os::Printer::log("COLLADA reading asset");
#endif

            // don't need asset data so far, so skip it
            skipSection(reader, false);
        }



        //! reads a <node> section and its content
        void CColladaFileLoader::readNodeSection(io::IXMLReaderUTF8* reader, scene::ISceneNode* parent)
        {
#ifdef COLLADA_READER_DEBUG
            os::Printer::log("COLLADA reading node");
#endif

            if (reader->isEmptyElement())
                return;

            core::stringw name = reader->getAttributeValue("name"); // name of the node
            core::matrix4 transform; // transformation of this node
            scene::ISceneNode* node = 0; // instance

            // read the node

            while(reader->read())
                if (reader->getNodeType() == io::EXN_ELEMENT)
                {
                    if (lookatNodeName == reader->getNodeName())
                        transform *= readLookAtNode(reader);
                    else
                        if (matrixNodeName == reader->getNodeName())
                            transform *= readMatrixNode(reader);
                        else
                            if (perspectiveNodeName == reader->getNodeName())
                                transform *= readPerspectiveNode(reader);
                            else
                                if (rotateNodeName == reader->getNodeName())
                                    transform *= readRotateNode(reader);
                                else
                                    if (scaleNodeName == reader->getNodeName())
                                        transform *= readScaleNode(reader);
                                    else
                                        if (translateNodeName == reader->getNodeName())
                                            transform *= readTranslateNode(reader);
                                        else		
                                            if (skewNodeName == reader->getNodeName())
                                                transform *= readSkewNode(reader);
                                            else
                                                if (instanceNodeName == reader->getNodeName())
                                                {
                                                    scene::ISceneNode* newnode = 0;
                                                    readInstanceNode(reader, parent, &newnode);

                                                    if (node && newnode)
                                                    {
                                                        // move children from dummy to new node
                                                        core::list<ISceneNode*>::Iterator it = node->getChildren().begin();
                                                        for (; it != node->getChildren().end(); it = node->getChildren().begin())
                                                            (*it)->setParent(newnode);

                                                        // remove previous dummy node
                                                        node->remove(); 
                                                    }

                                                    node = newnode;
                                                }
                                                else
                                                    if (nodeSectionName == reader->getNodeName())
                                                    {
                                                        // create dummy node if there is none yet.
                                                        if (!node)
                                                        {
                                                            scene::IDummyTransformationSceneNode* dummy = 
                                                                SceneManager->addDummyTransformationSceneNode(parent);
                                                            dummy->getRelativeTransformationMatrix() = transform;
                                                            node = dummy;
                                                        }

                                                        // read and add child
                                                        readNodeSection(reader, node);
                                                    }
                                                    else
                                                        skipSection(reader, true); // ignore all other sections

                } // end if node
                else
                    if (reader->getNodeType() == io::EXN_ELEMENT_END)
                    {
                        if (nodeSectionName == reader->getNodeName())
                            break;
                    }

                    if (node)
                    {
                        // TODO: set transformation correctly into node.
                        // currently this isn't done correctly. Need to get transformation,
                        // rotation and scale from the matrix.
                        core::vector3df trans = transform.getTranslation();
                        core::vector3df rot = transform.getRotationDegrees();

                        node->setPosition(trans);
                        node->setRotation(rot);
                        node->updateAbsolutePosition();

                        node->setName(name.c_str());
                    }
        }


        //! reads a <lookat> element and its content and creates a matrix from it
        core::matrix4 CColladaFileLoader::readLookAtNode(io::IXMLReaderUTF8* reader)
        {
            core::matrix4 mat;
            if (reader->isEmptyElement())
                return mat;

#ifdef COLLADA_READER_DEBUG
            os::Printer::log("COLLADA reading look at node");
#endif

            f32 floats[9];
            readFloatsInsideElement(reader, floats, 9);

            mat.buildCameraLookAtMatrixLH(
                core::vector3df(floats[0], floats[1], floats[2]),
                core::vector3df(floats[3], floats[4], floats[5]),
                core::vector3df(floats[6], floats[7], floats[8]));

            return mat;
        }

        //! reads a <skew> element and its content and creates a matrix from it
        core::matrix4 CColladaFileLoader::readSkewNode(io::IXMLReaderUTF8* reader)
        {
            core::matrix4 mat;
            if (reader->isEmptyElement())
                return mat;

            f32 floats[7];
            readFloatsInsideElement(reader, floats, 7);

            // TODO: build skew matrix from this 7 floats

            os::Printer::log("COLLADA loader warning: <skew> not implemented yet.", ELL_WARNING);

            return mat;
        }

        //! reads a <matrix> element and its content and creates a matrix from it
        core::matrix4 CColladaFileLoader::readMatrixNode(io::IXMLReaderUTF8* reader)
        {
            core::matrix4 mat;
            if (reader->isEmptyElement())
                return mat;

            readFloatsInsideElement(reader, mat.M, 16);

            return mat;
        }

        //! reads a <perspective> element and its content and creates a matrix from it
        core::matrix4 CColladaFileLoader::readPerspectiveNode(io::IXMLReaderUTF8* reader)
        {
            core::matrix4 mat;
            if (reader->isEmptyElement())
                return mat;

            f32 floats[1];
            readFloatsInsideElement(reader, floats, 1);

            // TODO: build perspecitve matrix from this f32

            os::Printer::log("COLLADA loader warning: <perspective> not implemented yet.", ELL_WARNING);

            return mat;
        }

        //! reads a <rotate> element and its content and creates a matrix from it
        core::matrix4 CColladaFileLoader::readRotateNode(io::IXMLReaderUTF8* reader)
        {
            core::matrix4 mat;
            if (reader->isEmptyElement())
                return mat;

            f32 floats[4];
            readFloatsInsideElement(reader, floats, 4);

            core::quaternion q(floats[0], floats[1], floats[2], floats[3]);
            mat = q.getMatrix();

            return mat;
        }

        //! reads a <scale> element and its content and creates a matrix from it
        core::matrix4 CColladaFileLoader::readScaleNode(io::IXMLReaderUTF8* reader)
        {
            core::matrix4 mat;
            if (reader->isEmptyElement())
                return mat;

            f32 floats[3];
            readFloatsInsideElement(reader, floats, 3);

            mat.setScale(core::vector3df(floats[0], floats[1], floats[2]));

            return mat;
        }


        //! reads a <translate> element and its content and creates a matrix from it
        core::matrix4 CColladaFileLoader::readTranslateNode(io::IXMLReaderUTF8* reader)
        {
            core::matrix4 mat;
            if (reader->isEmptyElement())
                return mat;

            f32 floats[3];
            readFloatsInsideElement(reader, floats, 3);

            mat.setTranslation(core::vector3df(floats[0], floats[1], floats[2]));

            return mat;
        }

        //! reads a <instance> node and creates a scene node from it
        void CColladaFileLoader::readInstanceNode(io::IXMLReaderUTF8* reader, scene::ISceneNode* parent, 
            scene::ISceneNode** outNode)
        {
#ifdef COLLADA_READER_DEBUG
            os::Printer::log("COLLADA reading instance");
#endif

            // find prefab of the specified id
            core::stringc url = reader->getAttributeValue("url");
            uriToId(url);

            if (CreateInstances)
                for (int i=0; i<(int)Prefabs.size(); ++i)
                    if (url == Prefabs[i]->getId())
                    {
                        *outNode = Prefabs[i]->addInstance(parent, SceneManager);
                        if (*outNode)
                            (*outNode)->setName(core::stringw(reader->getAttributeValue("id")).c_str());
                        return;
                    }
        }

        //! reads a <camera> element and stores it as prefab
        void CColladaFileLoader::readCameraPrefab(io::IXMLReaderUTF8* reader)
        {
            CCameraPrefab* prefab = new CCameraPrefab(reader->getAttributeValue("id"));

            if (!reader->isEmptyElement())
            {
                readColladaParameters(reader, cameraPrefabName);

                SColladaParam* p;

                p = getColladaParameter(ECPN_YFOV);
                if (p && p->Type == ECPT_FLOAT)
                    prefab->YFov = p->Floats[0];

                p = getColladaParameter(ECPN_ZNEAR);
                if (p && p->Type == ECPT_FLOAT)
                    prefab->ZNear = p->Floats[0];

                p = getColladaParameter(ECPN_ZFAR);
                if (p && p->Type == ECPT_FLOAT)
                    prefab->ZFar = p->Floats[0];
            }

            Prefabs.push_back(prefab);
        }


        //! reads a <material> element and stores it in the material section
        void CColladaFileLoader::readMaterial(io::IXMLReaderUTF8* reader)
        {
            SColladaMaterial material;
            material.Id = reader->getAttributeValue("id");

            if (!reader->isEmptyElement())
            {
                // TODO: implement material parsing later.
                // for now we only read and discard then all <param> tags.
                readColladaParameters(reader, materialSectionName);
            }

            // add material to list of loaded materials.
            Materials.push_back(material);
        }

        //! reads a <geometry> element and stores it as mesh if possible
        void CColladaFileLoader::readGeometry(io::IXMLReaderUTF8* reader)
        {
            core::stringc id = reader->getAttributeValue("id");

            core::stringc VertexPositionSource; // each mesh has exactly one <vertex> member, containing
            // a POSITION input. This string stores the source of this input.
            core::array<SSource> sources;
            bool okToReadArray = false;

            SAnimatedMesh* amesh = new SAnimatedMesh();
            scene::SMesh* mesh = new SMesh();
            amesh->addMesh(mesh);

            // read sources with arrays and accessor for each mesh

            if (!reader->isEmptyElement())
                while(reader->read())
                {
                    if (reader->getNodeType() == io::EXN_ELEMENT)
                    {
                        const char* nodeName = reader->getNodeName();
                        if (meshSectionName == nodeName)
                        {
                            // inside a mesh section. Don't have to do anything here.
                        }
                        else
                            if (sourceSectionName == nodeName)
                            {
                                // create a new source
                                sources.push_back(SSource());
                                sources.getLast().Id = reader->getAttributeValue("id");

#ifdef COLLADA_READER_DEBUG
                                //os::Printer::log("Loaded source", sources.getLast().Id.c_str());
#endif
                            }
                            else
                                if (arraySectionName == nodeName)
                                {
                                    // create a new array and read it.
                                    if (!sources.empty())
                                    {
                                        sources.getLast().Array.Name = reader->getAttributeValue("id");

                                        int count = reader->getAttributeValueAsInt("count");
                                        sources.getLast().Array.Data.set_used(count); // pre allocate

                                        // check if type of array is ok
                                        const char* type = reader->getAttributeValue("type");
                                        okToReadArray = (!strcmp("f32", type) || !strcmp("int", type));

#ifdef COLLADA_READER_DEBUG
                                        //os::Printer::log("Read array", sources.getLast().Array.Name.c_str());
#endif
                                    }
#ifdef COLLADA_READER_DEBUG
                                    else
                                        os::Printer::log("Warning, array found, but no source", 
                                        reader->getAttributeValue("id"));
#endif

                                }
                                else
                                    if (accessorSectionName == nodeName)
                                    {
                                        SAccessor accessor;
                                        accessor.Count = reader->getAttributeValueAsInt("count");
                                        accessor.Offset = reader->getAttributeValueAsInt("offset");
                                        accessor.Stride = reader->getAttributeValueAsInt("stride");
                                        if (accessor.Stride == 0)
                                            accessor.Stride = 1;

                                        // the accessor contains some information on how to access (boi!) the array,
                                        // the info is stored in collada style parameters, so just read them.
                                        readColladaParameters(reader, accessorSectionName);
                                        if (!sources.empty())
                                        {
                                            sources.getLast().Accessors.push_back(accessor);
                                            sources.getLast().Accessors.getLast().Parameters = Parameters;
                                        }
                                    }
                                    else
                                        if (verticesSectionName == nodeName)
                                        {
                                            // read vertex input position source
                                            readColladaInputs(reader, verticesSectionName);
                                            SColladaInput* input = getColladaInput(ECIS_POSITION);
                                            if (input)
                                                VertexPositionSource = input->Source;
                                        }
                                        else
                                            if (polygonsSectionName == nodeName)
                                            {
                                                // read polygons section
                                                readPolygonSection(reader, VertexPositionSource, sources, mesh);
                                            }

                    } // end if node type is element
                    else
                        if (reader->getNodeType() == io::EXN_TEXT)
                        {
                            // read array data
                            if (okToReadArray && !sources.empty())
                            {
                                core::array<f32>& a = sources.getLast().Array.Data;
                                core::stringc data = reader->getNodeData();
                                char* p = &data[0];

                                for (int i=0; i<(int)a.size(); ++i)
                                {
                                    findNextNoneWhiteSpace(&p);
                                    a[i] = readFloat(&p);
                                }
                            } // end reading array

                            okToReadArray = false;

                        } // end if node type is text
                        else
                            if (reader->getNodeType() == io::EXN_ELEMENT_END)
                            {
                                if (geometrySectionName == reader->getNodeName())
                                {
                                    // end of geometry section reached, cancel out
                                    break;
                                }
                            }		
                } // end while reader->read();

                // add mesh as geometry

                mesh->recalculateBoundingBox();

                // create virtual file name
                core::stringc filename = CurrentlyLoadingMesh;
                filename += '#';
                filename += id;

                // add to scene manager
                SceneManager->getMeshCache()->addMesh(filename.c_str(), amesh);
                amesh->drop();
                mesh->drop();

                os::Printer::log("Added COLLADA mesh", filename.c_str());	

                // create geometry prefab
                CGeometryPrefab* prefab = new CGeometryPrefab(id.c_str());
                prefab->Mesh = mesh;
                Prefabs.push_back(prefab);

                // store as dummy mesh if no instances will be created
                if (!CreateInstances && !DummyMesh)
                {
                    DummyMesh = amesh;
                    DummyMesh->grab();
                }
        }


        struct SInputSlot
        {
            f32* Data; // Pointer to source data
            ECOLLADA_INPUT_SEMANTIC Semantic;
        };

        struct SPolygon
        {
            core::array<int> Indices;
        };

        //! reads a polygons section and creates a mesh from it
        void CColladaFileLoader::readPolygonSection(io::IXMLReaderUTF8* reader,
            core::stringc vertexPositionSource, core::array<SSource>& sources,
            scene::SMesh* mesh)
        {
            core::stringc material = reader->getAttributeValue("material");
            int polygonCount = reader->getAttributeValueAsInt("count");
            core::array<SInputSlot> slots;
            core::array<SPolygon> polygons;
            bool parsePolygonOK = false;
            int inputSemanticCount = 0;

            // read all <input> and 
            if (!reader->isEmptyElement())
                while(reader->read())
                {
                    const char* nodeName = reader->getNodeName();

                    if (reader->getNodeType() == io::EXN_ELEMENT)
                    {
                        if (inputTagName == nodeName)
                        {
                            // read input tag
                            readColladaInput(reader);

                            // create new input slot
                            if (!Inputs.empty())
                            {
                                SInputSlot slot;
                                slot.Semantic = Inputs.getLast().Semantic;

                                core::stringc sourceArrayURI;

                                // get input source array id, if it is a vertex input, take
                                // the <vertex><input>-source attribute.
                                if (slot.Semantic == ECIS_VERTEX)
                                    sourceArrayURI = vertexPositionSource;
                                else
                                    sourceArrayURI = Inputs.getLast().Source;

                                uriToId(sourceArrayURI);

                                // find source array (we'll ignore acessors for this implementation)
                                int s;
                                for (s=0; s<(int)sources.size(); ++s)
                                    if (sources[s].Id == sourceArrayURI)
                                    {
                                        // slot found
                                        slot.Data = sources[s].Array.Data.pointer();
                                        break;
                                    }

                                    if (s == (int)sources.size())
                                        os::Printer::log("COLLADA Warning, polygon input source not found",
                                        sourceArrayURI.c_str());
                                    else
                                        slots.push_back(slot);

#ifdef COLLADA_READER_DEBUG
                                    // print slot
                                    // core::stringc tmp = "Added slot ";
                                    // tmp += inputSemanticNames[Inputs.getLast().Semantic];
                                    // tmp += " sourceArray:";
                                    // tmp += sourceArrayURI;
                                    // os::Printer::log(tmp.c_str());
#endif

                                    ++inputSemanticCount;
                            }
                        } // end is input node
                        else
                            if (polygonName == nodeName)
                            {
                                parsePolygonOK = true;
                                polygons.push_back(SPolygon());
                            } // end  is polygon node

                    } // end is element node
                    else
                        if (reader->getNodeType() == io::EXN_ELEMENT_END)
                        {
                            if (polygonName == nodeName)
                                parsePolygonOK = false; // end parsing a polygon
                            else
                                if (polygonsSectionName == nodeName)
                                    break; // cancel out and create mesh

                        } // end is element end
                        else
                            if (reader->getNodeType() == io::EXN_TEXT)
                            {
                                if (parsePolygonOK && polygons.size())
                                {
                                    // parse this text and add it to the last polygon data
                                    core::stringc data = reader->getNodeData();
                                    char* p = &data[0];
                                    SPolygon& poly = polygons.getLast();
                                    while(*p)
                                    {
                                        findNextNoneWhiteSpace(&p);
                                        poly.Indices.push_back(readInt(&p));
                                    }
                                    parsePolygonOK = false;
                                }
                            }

                } // end while reader->read()

                if (inputSemanticCount == 0 || inputSemanticCount != (int)slots.size())
                    return; // we cannot create the mesh if one of the input semantics wasn't found.

                if (!polygons.size())
                    return; // cancel if there are no polygons anyway.

                // based on input slots, create the mesh
                // TODO: currently we only support simple vertices, S3DVertex, but we could
                // support extend versions too, like S3DVertexTangents or S3DVertex2TCoords

                SMeshBuffer* buffer = new SMeshBuffer();

                int i;
                for (i=0; i<(int)polygons.size(); ++i)
                {
                    int vertexCount = polygons[i].Indices.size() / inputSemanticCount;

                    // for all vertices in array
                    for (int v=0; v<(int)polygons[i].Indices.size(); v+=inputSemanticCount)
                    {
                        video::S3DVertex vtx;
                        vtx.Color.set(100,255,255,255);

                        // for all input semantics
                        for (int k=0; k<(int)slots.size(); ++k)
                        {
                            // build vertex from input semantics.

                            int idx = polygons[i].Indices[v+k];

                            switch(slots[k].Semantic)
                            {
                            case ECIS_POSITION:
                            case ECIS_VERTEX:
                                vtx.Pos.X = slots[k].Data[(idx*3)+0];
                                vtx.Pos.Y = slots[k].Data[(idx*3)+1];
                                vtx.Pos.Z = slots[k].Data[(idx*3)+2];
                                break;
                            case ECIS_NORMAL:
                                vtx.Normal.X = slots[k].Data[(idx*3)+0];
                                vtx.Normal.Y = slots[k].Data[(idx*3)+1];
                                vtx.Normal.Z = slots[k].Data[(idx*3)+2];
                                break;
                            case ECIS_TEXCOORD:
                            case ECIS_UV:
                                vtx.TCoords.X = slots[k].Data[(idx*2)+0];
                                vtx.TCoords.Y = slots[k].Data[(idx*2)+1];
                                break;
                            case ECIS_TANGENT:
                                break;
                            }
                        }

                        buffer->Vertices.push_back(vtx);

                    } // end for all vertices

                    // add vertex indices 
                    int currentVertexCount = buffer->Vertices.size();
                    int oldVertexCount = currentVertexCount - vertexCount;
                    for (int face=0; face<vertexCount-2; ++face)
                    {
                        buffer->Indices.push_back(oldVertexCount + 0);
                        buffer->Indices.push_back(oldVertexCount + 1 + face);
                        buffer->Indices.push_back(oldVertexCount + 2 + face);
                    }

                } // end for all polygons

                // calculate normals if there is no slot for it
                for (i=0; i<(int)slots.size(); ++i)
                    if (slots[i].Semantic == ECIS_NORMAL)
                        break;

                if (i == (int)slots.size()) // no normals in input
                    SceneManager->getMeshManipulator()->recalculateNormals(buffer);

                // recalculate bounding box
                buffer->recalculateBoundingBox();

                // add mesh buffer
                mesh->addMeshBuffer(buffer);

                buffer->drop();
        }


        //! reads a <light> element and stores it as prefab
        void CColladaFileLoader::readLightPrefab(io::IXMLReaderUTF8* reader)
        {
            CLightPrefab* prefab = new CLightPrefab(reader->getAttributeValue("id"));

            if (!reader->isEmptyElement())
            {
                readColladaParameters(reader, lightPrefabName);

                SColladaParam* p = getColladaParameter(ECPN_COLOR);
                if (p && p->Type == ECPT_FLOAT3)
                    prefab->LightData.DiffuseColor.set(p->Floats[0], p->Floats[1], p->Floats[2]);
            }

            Prefabs.push_back(prefab);
        }


        //! returns a collada parameter or none if not found
        SColladaParam* CColladaFileLoader::getColladaParameter(ECOLLADA_PARAM_NAME name)
        {
            for (int i=0; i<(int)Parameters.size(); ++i)
                if (Parameters[i].Name == name)
                    return &Parameters[i];

            return 0;
        }

        //! returns a collada input or none if not found
        SColladaInput* CColladaFileLoader::getColladaInput(ECOLLADA_INPUT_SEMANTIC input)
        {
            for (int i=0; i<(int)Inputs.size(); ++i)
                if (Inputs[i].Semantic == input)
                    return &Inputs[i];

            return 0;
        }


        //! reads a collada input tag and adds it to the input parameter
        void CColladaFileLoader::readColladaInput(io::IXMLReaderUTF8* reader)
        {
            // parse param
            SColladaInput p;

            // get type
            int i;
            core::stringc semanticName = reader->getAttributeValue("semantic");
            for (i=0; inputSemanticNames[i]; ++i)
                if (semanticName == inputSemanticNames[i])
                {
                    p.Semantic = (ECOLLADA_INPUT_SEMANTIC)i;
                    break;
                }

                // get source
                p.Source = reader->getAttributeValue("source");

                // add input
                Inputs.push_back(p);
        }

        //! parses all collada inuts inside an element and stores them in Parameters
        void CColladaFileLoader::readColladaInputs(io::IXMLReaderUTF8* reader, const core::stringc& parentName)
        {
            Inputs.clear();

            while(reader->read())
            {
                if (reader->getNodeType() == io::EXN_ELEMENT &&
                    inputTagName == reader->getNodeName())
                {
                    readColladaInput(reader);
                }
                else
                    if (reader->getNodeType() == io::EXN_ELEMENT_END)
                    {
                        if (parentName == reader->getNodeName())
                            return; // end of parent reached
                    }

            } // end while reader->read();
        }

        //! parses all collada parameters inside an element and stores them in Parameters
        void CColladaFileLoader::readColladaParameters(io::IXMLReaderUTF8* reader,
            const core::stringc& parentName)
        {
            Parameters.clear();

            const char* const paramNames[] = {"COLOR", "AMBIENT", "DIFFUSE",
                "SPECULAR", "SHININESS", "YFOV", "ZNEAR", "ZFAR", 0};

            const char* const typeNames[] = {"f32", "float2", "float3", 0};

            while(reader->read())
            {
                const char* nodeName = reader->getNodeName();
                if (reader->getNodeType() == io::EXN_ELEMENT &&
                    paramTagName == nodeName)
                {
                    // parse param
                    SColladaParam p;

                    // get type
                    int i;
                    core::stringc typeName = reader->getAttributeValue("type");
                    for (i=0; typeNames[i]; ++i)
                        if (typeName == typeNames[i])
                        {
                            p.Type = (ECOLLADA_PARAM_TYPE)i;
                            break;
                        }

                        // get name
                        core::stringc nameName = reader->getAttributeValue("name");
                        for (i=0; typeNames[i]; ++i)
                            if (nameName == paramNames[i])
                            {
                                p.Name = (ECOLLADA_PARAM_NAME)i;
                                break;
                            }

                            // read parameter data inside parameter tags
                            switch(p.Type)
                            {
                            case ECPT_FLOAT:  
                            case ECPT_FLOAT2: 
                            case ECPT_FLOAT3: 
                            case ECPT_FLOAT4: 
                                readFloatsInsideElement(reader, p.Floats, p.Type - ECPT_FLOAT + 1); 
                                break;

                                // TODO: other types of data (ints, bools or whatever)
                            }

                            // add param
                            Parameters.push_back(p);
                }
                else
                    if (reader->getNodeType() == io::EXN_ELEMENT_END)
                    {
                        if (parentName == reader->getNodeName())
                            return; // end of parent reached
                    }

            } // end while reader->read();
        }


        //! parses a f32 from a char pointer and moves the pointer
        //! to the end of the parsed f32
        inline f32 CColladaFileLoader::readFloat(c8** p)
        {
            f32 ftmp;
            *p = core::fast_atof_move(*p, ftmp);
            return ftmp;
        }

        //! parses an int from a char pointer and moves the pointer to
        //! the end of the parsed f32
        inline s32 CColladaFileLoader::readInt(c8** p)
        {
            return (s32)readFloat(p);
        }

        //! places pointer to next begin of a token
        void CColladaFileLoader::findNextNoneWhiteSpace(c8** start)
        {
            c8* p = *start;

            while(*p && (*p==' ' || *p=='\n' || *p=='\r' || *p=='\t'))
                ++p;

            // TODO: skip comments <!-- -->

            *start = p;
        }


        //! reads floats from inside of xml element until end of xml element
        void CColladaFileLoader::readFloatsInsideElement(io::IXMLReaderUTF8* reader, f32* floats, s32 count)
        {
            if (reader->isEmptyElement())
                return;

            while(reader->read())
            {
                // TODO: check for comments inside the element
                // and ignore them.

                if (reader->getNodeType() == io::EXN_TEXT)
                {
                    // parse f32 data
                    core::stringc data = reader->getNodeData();
                    char* p = &data[0];

                    for (int i=0; i<count; ++i)
                    {
                        findNextNoneWhiteSpace(&p);
                        floats[i] = readFloat(&p);
                    }
                }
                else
                    if (reader->getNodeType() == io::EXN_ELEMENT_END)
                        break; // end parsing text
            }
        }


        //! clears all loaded data
        void CColladaFileLoader::clearData()
        {
            // delete all prefabs

            for (int i=0; i<(int)Prefabs.size(); ++i)
                Prefabs[i]->drop();

            Prefabs.clear();

            // clear all parameters
            Parameters.clear();

            // clear all materials
            Materials.clear();

            // clear all inputs
            Inputs.clear();
        }

        //! changes the XML URI into an internal id
        void CColladaFileLoader::uriToId(core::stringc& str)
        {
            // currently, we only remove the # from the begin if there
            // because we simply don't support referencing other files.
            if (!str.size())
                return;

            if (str[0] == '#')
                str.erase(0);
        }




    } // end namespace scene
} // end namespace irr

